#include <sys/epoll.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>

#include "event.h"
#include "event-internal.h"

#include "base-structure/linux-list.h"
#include "base-structure/linux-rbtree.h"

struct evepoll {
    struct rb_node rb_node;

    /* key */
    int fd;

    /* value */
    struct event *evread;
    struct event *evwrite;
};

static inline
int evepoll_compare_fn(const struct rb_node *n0,
                       const struct rb_node *n1) {
    const struct evepoll *x = (const struct evepoll*) n0;
    const struct evepoll *y = (const struct evepoll *) n1;
    return x->fd - y->fd;
}

#define INITIAL_EVENTS 32
#define MAX_EVENTS     4096

struct epollop {
    struct rb_root fds;

    struct epoll_event *events;
    int nevents;

    int epfd;
};

static int
epoll_add(void *args, struct event *ev) {
    struct epollop *epollop = (struct epollop*)args;
    int fd = ev->ev_fd;
    struct evepoll *evep;

    struct evepoll dummany;
    dummany.fd = fd;
    struct rb_node *res;
    rb_node_find(&epollop->fds, &dummany.rb_node, evepoll_compare_fn, res);
    evep = NULL;
    evep = rb_entry(res, struct evepoll, rb_node);

    if (evep == NULL || evep->fd != fd) {
        evep = calloc(1, sizeof(struct evepoll));
        evep->fd = fd;

        struct rb_node *duplicate_node = NULL;
        rb_node_add(&evep->rb_node, &epollop->fds, evepoll_compare_fn, duplicate_node);
        assert(duplicate_node == NULL);
    }

    int events = 0;
    int op = EPOLL_CTL_ADD;

    if (ev->ev_events & EV_READ) {
        events |= EPOLLIN;
    }
    if (ev->ev_events & EV_WRITE) {
        events |= EPOLLOUT;
    }
    if (evep->evread != NULL) {
        events |= EPOLLIN;
        op = EPOLL_CTL_MOD;
    }
    if (evep->evwrite != NULL) {
        events |= EPOLLOUT;
        op = EPOLL_CTL_MOD;
    }

    struct epoll_event epev;
    epev.data.fd = fd;
    epev.events = events;

    if ((epoll_ctl(epollop->epfd, op, fd, &epev)) == -1) {
        fprintf(stderr, "epoll_ctl error");
        return (-1);
    }

    if (ev->ev_events & EV_READ) {
        evep->evread = ev;
    }
    if (ev->ev_events & EV_WRITE) {
        evep->evwrite = ev;
    }

    return 0;
}

static int
epoll_del(void *args, struct event *ev) {
    struct epollop *epollop = (struct epollop *)args;
    int fd = ev->ev_fd;

    struct evepoll *evep;
    struct rb_root *root = &epollop->fds;

    struct evepoll dummy;
    dummy.fd = fd;
    struct rb_node *res;
    rb_node_find(root, &dummy.rb_node, evepoll_compare_fn, res);
    evep = rb_entry(res, struct evepoll, rb_node);
    assert(evep->fd == fd);

    int events = 0;
    if (ev->ev_events & EV_READ) {
        events |= EPOLLIN;
    }
    if (ev->ev_events & EV_WRITE) {
        events |= EPOLLOUT;
    }

    int op = EPOLL_CTL_DEL;
    int need_delete_read  = 1;
    int need_delete_write = 1;

    if ((events & (EPOLLIN | EPOLLOUT)) != (EPOLLIN | EPOLLOUT)) {
        if ((events & EPOLLIN) && evep->evwrite) {
            need_delete_write = 0;
            op = EPOLL_CTL_MOD;
            events = EPOLLOUT;
        }
        else if ((events & EPOLLOUT) && evep->evread) {
            need_delete_read = 0;
            op = EPOLL_CTL_MOD;
            events = EPOLLIN;
        }
    }

    struct epoll_event epev;
    epev.data.fd = fd;
    epev.events = events;

    if (need_delete_read) {
        evep->evread = NULL;
    }
    if (need_delete_write) {
        evep->evwrite = NULL;
    }
    if (need_delete_read && need_delete_write) {
        rb_erase(&evep->rb_node, root);
    }

    if ((epoll_ctl(epollop->epfd, op, fd, &epev)) < 0) {
        return -1;
    }

    return 0;
}

static void*
epoll_init(struct event_base *base) {
    int epfd;
    struct epollop *epollop;

    epfd = epoll_create(32000);
    if (epfd < 0) {
        return NULL;
    }

    epollop = malloc(sizeof(struct epollop));
    epollop->epfd = epfd;
    epollop->fds.rb_node = NULL;

    epollop->events = malloc(sizeof(struct epoll_event) * INITIAL_EVENTS);
    if (epollop->events == NULL) {
        free(epollop);
        return NULL;
    }
    epollop->nevents = INITIAL_EVENTS;

    return epollop;
}

static void
epoll_dealloc(void *args, struct event_base *base) {
    struct epollop* epollop = (struct epollop* )args;
    if (epollop->epfd >= 0) {
        close(epollop->epfd);
    }
    if (epollop->fds.rb_node) {
        epollop->fds.rb_node = NULL;
    }
    if (epollop->events) {
        free(epollop->events);
    }
    free(epollop);
}

static int
epoll_dispatch(void *args, struct event_base *base) {
    struct epollop *epollop = (struct epollop *)args;
    int n = epoll_wait(epollop->epfd, epollop->events, epollop->nevents, 1000);
    if (n < 0) {
        return (-1);
    }

    struct evepoll dummy;
    struct evepoll *evep;
    struct rb_node *res;

    for (int i = 0; i < n; i++) {
        struct event *evread  = NULL;
        struct event *evwrite = NULL;

        int fd = epollop->events[i].data.fd;
        dummy.fd = fd;
        rb_node_find(&epollop->fds, &dummy.rb_node, evepoll_compare_fn, res);
        evep = rb_entry(res, struct evepoll, rb_node);
        assert(evep->fd == fd);

        int events = epollop->events[i].events;
        if ((events & (EPOLLERR | EPOLLHUP))) {
            evread  = evep->evread;
            evwrite = evep->evwrite;
        }
        if ((events & EPOLLIN)) {
            evread = evep->evread;
        }
        if ((events & EPOLLOUT)) {
            evwrite = evep->evwrite;
        }

        if (!evread && !evwrite) {
            continue;
        }
        if (evread) {
            event_active(evread, EV_READ);
        }
        if (evwrite) {
            event_active(evwrite, EV_WRITE);
        }
    }

    if (n == epollop->nevents && n <= MAX_EVENTS) {
        int nevents = epollop->nevents * 2;
        struct epoll_event *events = realloc(epollop->events, sizeof(struct epoll_event) * nevents);
        if (events) {
            epollop->nevents = nevents;
            epollop->events = events;
        }
    }
    return 0;
}

const struct eventop epollops = {epoll_init, epoll_dealloc, epoll_dispatch, epoll_add, epoll_del};
